
#include <ostream>
#include <boost/date_time/posix_time/posix_time.hpp>
#include "Connection.h"
#include "ClientRequest.h"
#include "../engine/DebugStream.h"
#include "../engine/PackageFactory.h"
#include "../http-auth/http_author_util.h"
#include "../client-common/Config.h"

void CConnection::SendResponse(boost::shared_ptr<CResponse> resp)
{
	boost::mutex::scoped_lock lock(mSendMutex);

	resp->AddPropertie(string(PACKAGE_PROP_SESSION), mConfig.mSessionId);
	resp->AddPropertie(string(PACKAGE_PROP_DEVICE), mConfig.mDeviceId);

	mChannel->SetupRemote(mRemoteAddr, mRemotePort);
	mChannel->Send(resp);
}

boost::shared_ptr<CResponse> CConnection::SendRequest(boost::shared_ptr<CRequest> r, int try_count)
{
	Engine::DebugStream << "Send request: " + r->GetCommand() << '\n';

	boost::mutex::scoped_lock lock(mSendMutex);
	int n = 0;
	while(n < try_count) {
		
		if(mConfig.mDeviceId.length() != 0) 
			r->AddPropertie(string(PACKAGE_PROP_DEVICE), mConfig.mDeviceId);
		if(mConfig.mSessionId.length() != 0) 
			r->AddPropertie(string(PACKAGE_PROP_SESSION), mConfig.mSessionId);
		if(mConfig.mToken.length() != 0) 
			r->AddPropertie(string(PACKAGE_PROP_TOKEN), mConfig.mToken);

		string ip;
		mChannel->GetLocalIPAddress(ip);
		r->AddPropertie(string(PACKAGE_PROP_HOST_IP), ip);

		mChannel->SetupRemote(mRemoteAddr, mRemotePort);
		mChannel->Send(r);

		mRequest = r;
		mResponse.reset();

		try {
			WaitForResponse();	
		}
		catch(std::exception&) {
			mRequest.reset();
			n++;
			continue;
		}

		mRequest.reset();

		if(mResponse->GetStatus() == RESPONSE_STATUS_SessionNotFound) {
			mConfig.mToken.clear();
			r->DelPropertie(string(PACKAGE_PROP_TOKEN));
			Client::Common::CConfig& conf = Client::Common::CConfig::GetInstance();
			if(conf.mAccount.length() == 0) break;
			r->DelPropertie(string(PACKAGE_PROP_AUTHORIZATION));
			r->AddPropertie(string(PACKAGE_PROP_SET_SESSION), conf.mAccount + '@' + conf.mPassword);
			r->UpdateMagic();
			mResponse.reset();
		}
		else
		if(mResponse->GetStatus() == RESPONSE_STATUS_Unauthozied) {
			mConfig.mToken.clear();
			r->DelPropertie(string(PACKAGE_PROP_TOKEN));
			AddAuthorization(r, mResponse);
			string& serv_ip = mResponse->GetPropertie(string(PACKAGE_PROP_SERV_IP));
			if(serv_ip.length() > 0) r->AddPropertie(string(PACKAGE_PROP_SERV_IP), serv_ip);
			r->DelPropertie(string(PACKAGE_PROP_SET_SESSION));
			r->UpdateMagic();
			mResponse.reset();
			n++;
		}
		else {
			break;
		}
	} // for()

	if(n == try_count) {
		Engine::DebugStream << "send request failed : " + r->GetCommand() << 'n';
	}

	return mResponse;
}

void CConnection::AddAuthorization(boost::shared_ptr<CRequest> request, boost::shared_ptr<CResponse> response)
{
	if(!response->HasAuthorization()) {
		DebugStream << "Authentication header not found";
		return;
	}

	if(response->GetAuthParameter(string(AUTH_INFO_TYPE)) != AUTH_TYPE_DIGEST) {
		DebugStream << "Unsupported authentication type : " + response->GetAuthParameter(string(AUTH_INFO_TYPE));
		return;
	}

	string& algorithm = response->GetAuthParameter(string(AUTH_INFO_ALGORITHM));
	if(algorithm.length() > 0 && algorithm != AUTH_ALGORITHN_MD5) {
		DebugStream << "Unsupported authentication algorithm : " + algorithm;
		return; 
	}

	Client::Common::CConfig& conf = Client::Common::CConfig::GetInstance();

	HTTPAuth_Config cfg;
	memset(&cfg, 0, sizeof(cfg));
	cfg.pcUserName = (char*)conf.mAccount.c_str();
	cfg.pcPassword = (char*)conf.mPassword.c_str();
	cfg.eAuthType = E_HTTPAUTH_TYPE_DIGEST;
	cfg.eAbility = HTTPDigest_Config_Algorithm_MD5;
	cfg.pcMethod = (char*)request->GetCommand().c_str();
	cfg.pcUri = "/";
	
	string auth_info = response->GetPropertie(string(PACKAGE_PROP_AUTHENTICATE));
	char* authorization;
	int ret = HTTPAuth_BuildAuthorizationHeader(&cfg, auth_info.c_str(), &authorization);
	if(ret != E_HTTPAUTH_ERR_NONE) {
		DebugStream << "Build Authorization Header failed with code " << ret;
		return;
	}

	request->AddPropertie(string(PACKAGE_PROP_AUTHORIZATION), string(authorization));

	delete authorization;
}

using namespace boost::posix_time;

void CConnection::WaitForResponse()
{
	if(mRecvThread) {
		boost::mutex::scoped_lock lock(mWaitMutex);
		millisec_posix_time_system_config::time_duration_type wait_time = millisec(5000);
		mWaitCond.timed_wait(lock, wait_time);
	}
	else {
		boost::shared_ptr<CPackage> p = ReceivePackage();
		if(p && !p->IsRequest()) {
			SetResponse(boost::dynamic_pointer_cast<CResponse, CPackage>(p));
		}
		else {
			DebugStream << "CConnection::WaitForResponse - Response not received\n";
		}
	}

	if(!mResponse) throw CConnectionException() << connection_error("Response not received");
}

void CConnection::SetResponse(boost::shared_ptr<CResponse> resp)
{
	if(!mRequest) return;

	boost::mutex::scoped_lock lock(mWaitMutex);

	if(resp->MatchRequest(mRequest)) {
		mResponse = resp;

		string& session_id =  mResponse->GetPropertie(string(PACKAGE_PROP_SET_SESSION));
		if(session_id.length() > 0) mConfig.mSessionId = session_id;
		string& token = mResponse->GetPropertie(string(PACKAGE_PROP_SET_TOKEN));
		if(token.length() > 0) mConfig.mToken = token;

		mWaitCond.notify_all();
	}

}

void CConnection::Close()
{
	mClosed = true;
	mChannel->Close();

	{
		boost::mutex::scoped_lock lock(mWaitMutex);
		mWaitCond.notify_all();
	}

	if(mRecvThread) mRecvThread->join();
}

boost::shared_ptr<CPackage> CConnection::ReceivePackage()
{
	boost::shared_ptr<CPackage> p;
	while(!p) {
		p = mChannel->ReceivePackage();
	}

	return p;
}

void CConnection::OnReceive(PackageRef p)
{
	if(!p->IsRequest()) {
		SetResponse(boost::dynamic_pointer_cast<CResponse, CPackage>(p));
	}
	else {
		boost::shared_ptr<CClientRequest> r = boost::dynamic_pointer_cast<CClientRequest, CPackage>(p);
		r->SetConnection(boost::dynamic_pointer_cast<CConnection, IPackageReceiver>(shared_from_this()));
		r->DoRequest();
	}
}

